// ***********************************************************************
// Copyright (c) 2021 SZ,All rights reserved.
// CLR Version      : 0.0.1
// Project          : Tools
// Assembly         : Tools
// Author           : Jimmy Teng
// Created          : 2019-08-28
// Description      : Async Timer
// ***********************************************************************
// Last Modified By : Jimmy Teng
// Last Modified On : 2019-08-28
// ***********************************************************************
#ifndef ASYNC_TIMER_HPP
#define ASYNC_TIMER_HPP
#include <cstdint>
#include <chrono>
#include <map>
#include <vector>
#include <mutex>
#include <iostream>
#include "P7_Trace.h"
#include "P7_Telemetry.h"
class AsyncLogger {
  AsyncLogger() = default;
 public:
  int Initialize(const std::string &str = "127.0.0.1") {
    stTelemetry_Conf l_stConf = {};
    P7_Set_Crash_Handler();
    //create P7 client object
    p_client_ = P7_Create_Client(TM(("/P7.Sink=Auto /P7.Addr=" + str).c_str()));
    if (NULL == p_client_) {
      goto l_lblExit;
    }
    l_stConf.pContext = nullptr;
    l_stConf.pTimestamp_Callback = nullptr;
    l_stConf.qwTimestamp_Frequency = 0ull;
    //create P7 telemetry object
    p_telemetry_ = P7_Create_Telemetry(p_client_, TM("Timer"), &l_stConf);
    if (nullptr == p_telemetry_) {
      goto l_lblExit;
    }
    init_flg = true;
    return 0;
    l_lblExit:
    if (p_telemetry_) {
      p_telemetry_->Release();
      p_telemetry_ = nullptr;
    }

    if (p_trace_) {
      p_trace_->Release();
      p_trace_ = nullptr;
    }

    if (p_client_) {
      p_client_->Release();
      p_client_ = nullptr;
    }

    return -1;
  }
  /// \brief GetInstance
  /// \return Instance Ptr
  static AsyncLogger *GetInstance() {
    static auto *instance = new AsyncLogger();
    return instance;
  }
  /// \brief Start Timer with timer id and call index
  /// \param id timer id
  /// \param index call index
  /// \return timestamp
  uint64_t TimerStart(const std::string &name, uint32_t index) {
    if (!init_flg) {
      return -1;
    }
    uint64_t id = 0;
    std::lock_guard<std::mutex> lock(mtx_);
    auto name_iter = timer_name_id_map_.find(name);
    if (name_iter == timer_name_id_map_.end()) {
      id = timer_name_id_map_.size();
      timer_name_id_map_.insert(std::make_pair(name, id));
    } else {
      id = name_iter->second;
    }
    uint64_t timer_index = id << 32 | index;
    auto iter_send = timer_id_sendid_map_.find(id);
    if (iter_send == timer_id_sendid_map_.end()) {
      uint16_t send_id;
      std::string topic = "FunctionTimer/" + name + " (ms)";
      p_telemetry_->Create(TM(topic.c_str()), 0, 0, 100, 100, 1, &send_id);
      timer_id_sendid_map_.insert(std::make_pair(id, send_id));
      //std::cout << "send_id created " << send_id << std::endl;
    }
    auto timestamp =
        std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
    timer_id_starttime_map_.insert(std::make_pair(timer_index, timestamp));
    return timestamp;
  }
  /// \brief End Timer with timer id and call index
  /// \param id timer id
  /// \param index call index
  /// \param during timer during
  /// \param valid  timer valid
  /// \return timestamp
  uint64_t TimerEnd(const std::string &name, uint32_t index) {
    uint64_t during;
    if (!init_flg) {
      return -1;
    }
    auto timestamp =
        std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();

    std::lock_guard<std::mutex> lock(mtx_);
    uint64_t id = 0;
    auto name_iter = timer_name_id_map_.find(name);
    if (name_iter == timer_name_id_map_.end()) {
      return timestamp;
    } else {
      id = name_iter->second;
    }
    auto iter = timer_id_starttime_map_.find(id << 32 | index);
    if (iter == timer_id_starttime_map_.end()) {
      return timestamp;
    } else {
      during = timestamp - iter->second;
    }
    auto send_iter = timer_id_sendid_map_.find(id);
    if (send_iter != timer_id_sendid_map_.end()) {
      p_telemetry_->Add(send_iter->second, ((double) during / 1000));
    }
    return timestamp;
  }
  /// \brief clear Timer
  /// \return
  void Clear() {
    std::lock_guard<std::mutex> lock(mtx_);
    timer_id_starttime_map_.clear();
    //time_statistics.clear();
  }
  int AddVariable(const std::string &name, double variable) {
    uint16_t send_id;
    auto name_iter = variable_name_sendid_map_.find(name);
    if (name_iter == variable_name_sendid_map_.end()) {
      p_telemetry_->Create(TM(("Variable/" + name).c_str()), 0, 0, 100, 100, 1, &send_id);
      variable_name_sendid_map_.insert(std::make_pair(name, send_id));
    } else {
      send_id = name_iter->second;
    }

    if (p_telemetry_->Add(send_id, variable)) {
      return 1;
    } else {
      return 0;
    }
  }
  int AddCount(const std::string &name) {
    uint16_t send_id;
    uint32_t count;
    auto name_iter = count_name_sendid_map_.find(name);
    if (name_iter == count_name_sendid_map_.end()) {

      p_telemetry_->Create(TM(("Count/" + name).c_str()), 0, 0, 100000, 100000, 1, &send_id);
      count_name_sendid_map_.insert(std::make_pair(name, send_id));
      count = 1;
      count_name_number_map_.insert(std::make_pair(name, count));
    } else {
      send_id = name_iter->second;
      auto count_iter = count_name_number_map_.find(name);
      count_iter->second = count_iter->second + 1;
      count = (count_iter->second);
    }

    if (p_telemetry_->Add(send_id, count)) {
      return 1;
    } else {
      return 0;
    }
  }
 private:
  bool init_flg = false;
  std::mutex mtx_;
  std::map<std::string, uint16_t> variable_name_sendid_map_;
  std::map<std::string, uint16_t> count_name_sendid_map_;
  std::map<std::string, uint32_t> count_name_number_map_;
  std::map<std::string, uint32_t> timer_name_id_map_;
  std::map<uint64_t, uint64_t> timer_id_sendid_map_;
  std::map<uint64_t, uint64_t> timer_id_starttime_map_;
  IP7_Client *p_client_ = nullptr;
  IP7_Trace *p_trace_ = nullptr;
  IP7_Telemetry *p_telemetry_ = nullptr;
};
static std::mutex timer_mtx;
static uint32_t timer_count = 0;
class Timer {
 public:
  Timer(const std::string &name) : name_(name) {
    {
      std::lock_guard<std::mutex> lock(timer_mtx);
      index = timer_count++;
    }
    AsyncLogger::GetInstance()->TimerStart(name_, index);
  }
  ~Timer() {
//    std::lock_guard<std::mutex> lock(timer_mtx);
    AsyncLogger::GetInstance()->TimerEnd(name_, index);
  };
 private:
  uint32_t index;
  std::string name_;
};

//************************************
// Method:    string_replace
// FullName:  string_replace
// Access:    public
// Returns:   void
// Qualifier: 把字符串的strsrc替换成strdst
// Parameter: std::string & strBig
// Parameter: const std::string & strsrc
// Parameter: const std::string & strdst
//************************************
static void string_replace(std::string &strBig, const std::string &strsrc, const std::string &strdst) {
  std::string::size_type pos = 0;
  std::string::size_type srclen = strsrc.size();
  std::string::size_type dstlen = strdst.size();

  while ((pos = strBig.find(strsrc, pos)) != std::string::npos) {
    strBig.replace(pos, srclen, strdst);
    pos += dstlen;
  }
}

//************************************
// Method:    GetFileOrURLShortName
// FullName:  GetFileOrURLShortName
// Access:    public
// Returns:   std::string
// Qualifier: 获取路径或URL的文件名（包括后缀，如 C:\Test\abc.xyz --> abc.xyz）
// Parameter: std::string strFullName
//************************************
static std::string GetPathOrURLShortName(std::string strFullName) {
  if (strFullName.empty()) {
    return "";
  }

  string_replace(strFullName, "/", "\\");

  std::string::size_type iPos = strFullName.find_last_of('\\') + 1;

  return strFullName.substr(iPos, strFullName.length() - iPos);
}

#if NOLOG
AL_VARIABLE(name, variable)
#define AL_ADD_COUNT(name)
#define AL_FUNCTION_TIMER
#else
#define AL_VARIABLE(name, variable) AsyncLogger::GetInstance()->AddVariable((name),(variable));
#define AL_ADD_COUNT(name) AsyncLogger::GetInstance()->AddCount((name));
#define AL_FUNCTION_TIMER volatile Timer  timer(GetPathOrURLShortName(std::string(__FILE__))+":"+std::string(__FUNCTION__));
#endif

#endif //ASYNC_TIMER_HPP
